/**
 *    @file       cfile.cpp
 *
 *    @brief      
 *    @details
 *
 *    @version    1.0
 *    @date       12/10/2012 07:38:59 PM
 *
 *    @author     Theophilus, wangtf418@gmail.com
 */
#include <sys/types.h>
#include <unistd.h>
#include <utime.h>
#include <stdarg.h>
#include <errno.h>
#include "cfile.h"

cfile::cfile()
	: m_file(NULL), m_filestat(NULL), m_errno(0)
{

}

cfile::cfile(const std::string &filename)
	: m_filename(filename), m_file(NULL), m_filestat(NULL), m_errno(0)
{

}

cfile::~cfile()
{
	this->close();
	if (m_filestat)
	{
		delete m_filestat;
		m_filestat = NULL;
	}
}

void cfile::set_filename(const std::string &filename)
{
	m_filename = filename;
}

std::string & cfile::get_filename()
{
	return m_filename;
}

FILE * cfile::get_file()
{
	return m_file;
}

struct stat * cfile::stat()
{
	m_errno = 0;
	if (m_filename.empty())
	{
		return NULL;
	}
	struct stat file_stat;
	bf_int_t ret = ::stat(m_filename.c_str(), &file_stat);
	if (ret != 0)
	{
		m_errno = errno;
		return NULL;
	}
	if (!m_filestat)
	{
		m_filestat = new struct stat;
	}
	memcpy(m_filestat, &file_stat, sizeof(file_stat));
	return m_filestat;
}

struct stat * cfile::get_filestat()
{
	if (m_filestat)
	{
		return m_filestat;
	}
	else
	{
		return this->stat();
	}
}

bf_int_t cfile::set_filetime(bf_time_t modify_time, bf_time_t access_time)
{
	m_errno = 0;
	bf_time_t now_time = time(NULL);
	struct utimbuf utimebuf_tmp;
	utimebuf_tmp.modtime = (modify_time == -1)? now_time:modify_time;
	utimebuf_tmp.actime = (access_time == -1)? now_time:access_time;
	bf_int_t ret = utime(m_filename.c_str(), &utimebuf_tmp);
	if (ret != 0)
	{
		m_errno = errno;
		return BF_ERROR;
	}
	return BF_OK;
}

bf_int_t cfile::open(const std::string &mode)
{
	m_errno = 0;
	if (m_filename.empty() || mode.empty())
	{
		return BF_ERROR;
	}

	bf_int_t ret = this->close();
	if (ret != BF_OK)
	{
		return BF_ERROR;
	}

	m_file = fopen(m_filename.c_str(), mode.c_str());
	if (!m_file)
	{
		return BF_ERROR;
	}
	return BF_OK;
}

bf_int_t cfile::close()
{
	m_errno = 0;
	if (m_file)
	{
		int ret = fclose(m_file);
		if (ret != 0)
		{
			m_errno = errno;
			return BF_ERROR;
		}
		m_file = NULL;
	}
	return BF_OK;
}

bf_int_t cfile::seek(bf_long_t seek_offset, bf_int_t whence /* = SEEK_SET */)
{
	m_errno = 0;
	if (!m_file)
	{
		return BF_ERROR;
	}
	bf_int_t ret = fseek(m_file, seek_offset, whence);
	if (ret == -1) 
	{
		m_errno = errno;
		return BF_ERROR;
	}
	return BF_OK;
}

bf_int_t cfile::read(void *read_buf, bf_size_t read_len)
{
	m_errno = 0;
	if (!m_file || !read_buf)
	{
		return BF_ERROR;
	}
	bf_int_t ret = fread(read_buf, read_len, 1, m_file);
	if (ret != 1)
	{
		m_errno = errno;
		return BF_ERROR;
	}
	return BF_OK;
}

bf_int_t cfile::write(const void *write_buf, bf_size_t write_len)
{
	m_errno = 0;
	if (!m_file || !write_buf || write_len < 0)
	{
		return BF_ERROR;
	}
	bf_int_t ret = fwrite(write_buf, write_len, 1, m_file);
	if (ret != 1)
	{
		m_errno = errno;
		return BF_ERROR;
	}
	return BF_OK;
}

bf_int_t cfile::print(const bf_char_t *format, ...)
{
	if (!m_file || !format)
	{
		return BF_ERROR;
	}
	bf_valist_t ap;
	va_start(ap, format);
	bf_int_t ret = vfprintf(m_file, format, ap);
	va_end(ap);
	if (ret < 0)
	{
		return BF_ERROR;
	}
	return ret;
}

bf_int_t cfile::vprint(const bf_char_t *format, bf_valist_t &ap)
{
	if (!m_file || !format)
	{
		return BF_ERROR;
	}
	bf_int_t ret = vfprintf(m_file, format, ap);
	if (ret < 0)
	{
		return BF_ERROR;
	}
	return ret;
}

bf_int_t cfile::flush()
{
	m_errno = 0;
	if (!m_file)
	{
		return BF_ERROR;
	}
	bf_int_t ret = fflush(m_file);
	if (ret != 0)
	{
		m_errno = errno;
		return BF_ERROR;
	}
	return BF_OK;
}

//static
bf_int_t cfile::remove()
{
	m_errno = 0;
	if (m_filename.empty())
	{
		return BF_ERROR;
	}

	bf_int_t ret = ::remove(m_filename.c_str());
	if (ret == 0)
	{
		return BF_OK;
	}
	m_errno = errno;
	return (m_errno == ENOENT)? BF_OK:BF_ERROR;
}

bf_int_t cfile::move(const std::string &dst_filename, bf_bool_t ifoverwrite /* = false */ )
{
	m_errno = 0;
	if (m_filename.empty() || dst_filename.empty())
	{
		return BF_ERROR;
	}
	if (is_exist() != BF_TRUE)
	{
		return BF_ERROR;
	}
	cfile dstfile(dst_filename);
	if (!ifoverwrite && dstfile.is_exist() == BF_TRUE)
	{
		return BF_ERROR;
	}

	bf_int_t ret = rename(m_filename.c_str(), dst_filename.c_str());
	if (ret == 0)
	{
		return BF_OK;
	}
	if (errno != EXDEV)
	{
		return BF_ERROR;
	}

	if (open("r") != BF_OK || dstfile.open("w") != BF_OK)
	{
		close();
		return BF_ERROR;
	}

	struct stat *src_stat = get_filestat();
	bf_size_t filelen = (bf_size_t)src_stat->st_size;
	bf_size_t blocklen = (filelen > 4194304)? 4194304:filelen;
	bf_char_t *block = new bf_char_t[blocklen];
	bf_size_t restlen = filelen;
	while (restlen > 0)
	{
		bf_size_t readlen = (restlen > blocklen)? blocklen:restlen;
		if (read(block, readlen) != BF_OK || dstfile.write(block, readlen) != BF_OK)
		{
			break;
		}
		restlen -= readlen;
	}
	close();
	dstfile.close();

	if (restlen > 0)
	{
		return BF_ERROR;
	}

	return remove();
}
bf_int_t cfile::copy(const std::string &dst_filename, bf_bool_t ifoverwrite /* = false */)
{
	m_errno = 0;
	if (m_filename.empty() || dst_filename.empty())
	{
		return BF_ERROR;
	}
	if (is_exist() != BF_TRUE)
	{
		return BF_ERROR;
	}
	cfile dstfile(dst_filename);
	if (!ifoverwrite && dstfile.is_exist() == BF_TRUE)
	{
		return BF_ERROR;
	}

	if (open("r") != BF_OK || dstfile.open("w") != BF_OK)
	{
		close();
		return BF_ERROR;
	}

	struct stat *src_stat = get_filestat();
	bf_size_t filelen = (bf_size_t)src_stat->st_size;
	bf_size_t blocklen = (filelen > 4194304)? 4194304:filelen;
	bf_char_t *block = new bf_char_t[blocklen];
	bf_size_t restlen = filelen;
	while (restlen > 0)
	{
		bf_size_t readlen = (restlen > blocklen)? blocklen:restlen;
		if (read(block, readlen) != BF_OK || dstfile.write(block, readlen) != BF_OK)
		{
			break;
		}
		restlen -= readlen;
	}
	close();
	dstfile.close();

	if (restlen > 0)
	{
		return BF_ERROR;
	}
	return BF_OK;
}

bf_int_t cfile::is_exist()
{
	if (get_filestat() == NULL)
	{
		return (m_errno == ENOENT)? BF_FALSE:BF_ERROR;
	}

	return BF_TRUE;
}

bf_int_t cfile::is_dir()
{
	struct stat *fstat = get_filestat(); 
	if (!fstat)
	{
		return BF_ERROR;
	}
	if (S_ISDIR(fstat->st_mode))
	{
		return BF_TRUE;
	}
	return BF_FALSE;
}

bf_int_t cfile::is_file()
{
	struct stat *fstat = get_filestat(); 
	if (!fstat)
	{
		return BF_ERROR;
	}
	if (S_ISREG(fstat->st_mode))
	{
		return BF_TRUE;
	}
	return BF_FALSE;
}

bf_int_t cfile::is_link()
{
	struct stat *fstat = get_filestat(); 
	if (!fstat)
	{
		return BF_ERROR;
	}
	if (S_ISLNK(fstat->st_mode))
	{
		return BF_TRUE;
	}
	return BF_FALSE;

}
